﻿using System;
using System.Net;
using System.Text;

namespace Storage.Core
{
	internal class FastBinarySerializer : IBinarySerializer
	{
		public FastBinarySerializer(int initialCapacity, int growthInBytes)
		{
			this.growthInBytes = growthInBytes;
			Reset(initialCapacity);
		}

		public FastBinarySerializer(byte[] buffer, int growthInBytes)
		{
			this.buffer = buffer;
			this.growthInBytes = growthInBytes;
			Reset();
		}

		public FastBinarySerializer(int initialCapacity) : this(initialCapacity, DefaultGrowthInBytes) { }
		public FastBinarySerializer(byte[] buffer) : this(buffer, DefaultGrowthInBytes) {}

		public void Reset(int neededCapacity = 0)
		{
			if(buffer == null || buffer.Length < neededCapacity + 4)
				buffer = new byte[neededCapacity + 4];
			offset = 0;
			length = 0;
		}

		public int Position { get { return offset; } }
		public bool CanSeek { get { return true; } }

		public IBinarySerializer SeekToStart() { return Seek(4); }
		public IBinarySerializer SeekToEnd() { return Seek(length); }

		public IBinarySerializer Seek(int position)
		{
			offset = position;
			return this;
		}

		public IBinarySerializer Write(bool b)
		{
			return Write((b ? 1 : 0));
		}

		public IBinarySerializer Write(byte b)
		{
			EnsureCapacity(1);
			buffer[offset++] = b;
			if (offset > length)
				length = offset;
			return this;
		}

		public IBinarySerializer Write(short i)
		{
			EnsureCapacity(2);
			buffer[offset++] = (byte)i;
			buffer[offset++] = (byte)(i >> 8);
			if (offset > length)
				length = offset;
			return this;
		}

		// Order of magnitude faster than BitConverter..
		public IBinarySerializer Write(Int32 i)
		{
			EnsureCapacity(4);
			buffer[offset++] = (byte)i;
			buffer[offset++] = (byte)(i >> 8);
			buffer[offset++] = (byte)(i >> 16);
			buffer[offset++] = (byte)(i >> 24);
			if(offset > length)
				length = offset;
			return this;
		}

		public IBinarySerializer Write(Int64 i)
		{
			EnsureCapacity(8);
			buffer[offset++] = (byte)i;
			buffer[offset++] = (byte)(i >> 8);
			buffer[offset++] = (byte)(i >> 16);
			buffer[offset++] = (byte)(i >> 24);
			buffer[offset++] = (byte)(i >> 32);
			buffer[offset++] = (byte)(i >> 40);
			buffer[offset++] = (byte)(i >> 48);
			buffer[offset++] = (byte)(i >> 56);
			if (offset > length)
				length = offset;
			return this;
		}

		public IBinarySerializer Write(UInt16 i) { return Write((Int16)i); }
		public IBinarySerializer Write(UInt32 i) { return Write((Int32)i); }
		public IBinarySerializer Write(UInt64 i) { return Write((Int64)i); }

		public IBinarySerializer Write(float f)
		{
			var bytes = BitConverter.GetBytes(f);
			EnsureCapacity(bytes.Length);
			System.Buffer.BlockCopy(bytes, 0, buffer, offset, bytes.Length);
			offset += bytes.Length;
			if (offset > length)
				length = offset;
			return this;
		}

		public IBinarySerializer Write(double d)
		{
			return Write(BitConverter.DoubleToInt64Bits(d));
		}

		public IBinarySerializer Write(string s)
		{
			return Write(s, Encoding.UTF8);
		}

		public IBinarySerializer Write(string s, Encoding encoding)
		{
			EnsureCapacity(encoding.GetMaxByteCount(s.Length) + sizeof(Int32));
			var startOffset = offset;
			Write(0);
			var len = encoding.GetBytes(s, 0, s.Length, buffer, offset);
			var endOffset = offset + len;
			offset = startOffset;
			Write(len);
			offset = endOffset;
			if (offset > length)
				length = offset;
			return this;
		}

		public IBinarySerializer Write(byte[] b, int byteOffset, int count)
		{
			EnsureCapacity(count + sizeof(Int32));
			Write(count);
			System.Buffer.BlockCopy(b, byteOffset, buffer, offset, count);
			offset += count;
			if (offset > length)
				length = offset;
			return this;
		}

		public IBinarySerializer Write(byte[] b) { return Write(b, 0, b.Length); }

		public IBinarySerializer Write(Guid guid)
		{
			var guidBytes = guid.ToByteArray();
			EnsureCapacity(guidBytes.Length);
			System.Buffer.BlockCopy(guidBytes, 0, buffer, offset, guidBytes.Length);
			offset += guidBytes.Length;
			if (offset > length)
				length = offset;
			return this;
		}

		public IBinarySerializer Write(IPAddress ip)
		{
			return Write(ip.GetAddressBytes());
		}

		public IBinarySerializer Write(IPEndPoint endPoint)
		{
			Write(endPoint.Address);
			return Write(endPoint.Port);
		}

		public IBinarySerializer Write(TimeSpan ts)
		{
			return Write(ts.Ticks);
		}

		public IBinarySerializer Write(DateTime dt)
		{
			return Write(dt.ToBinary());
		}

		public byte[] Buffer { get { return buffer; } }
		public int Length { get { return length; } }

		private void EnsureCapacity(int neededBytes)
		{
			if (buffer.Length - offset >= neededBytes)
				return;
			var tmp = new byte[buffer.Length + Math.Max(neededBytes, growthInBytes)];
			buffer.CopyTo(tmp, 0);
			buffer = tmp;
		}

		private byte[] buffer;
		private int offset;
		private int length;
		private readonly int growthInBytes;

		internal const int DefaultGrowthInBytes = 32 * 1024;
	}
}
